Security News
Input Validation Vulnerabilities Dominate MITRE's 2024 CWE Top 25 List
MITRE's 2024 CWE Top 25 highlights critical software vulnerabilities like XSS, SQL Injection, and CSRF, reflecting shifts due to a refined ranking methodology.
@webcomponents/custom-elements
Advanced tools
@webcomponents/custom-elements is a polyfill for the Custom Elements v1 specification. It allows developers to define new HTML elements in a framework-agnostic way, enabling the creation of reusable and encapsulated components.
Defining a Custom Element
This feature allows you to define a new custom HTML element by extending the HTMLElement class. The custom element can then be used in HTML just like any standard element.
class MyElement extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.shadowRoot.innerHTML = '<p>Hello, World!</p>';
}
}
customElements.define('my-element', MyElement);
Using Lifecycle Callbacks
This feature allows you to use lifecycle callbacks such as connectedCallback and disconnectedCallback to run code when the element is added or removed from the DOM.
class MyElement extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
}
connectedCallback() {
this.shadowRoot.innerHTML = '<p>Element added to the DOM</p>';
}
disconnectedCallback() {
console.log('Element removed from the DOM');
}
}
customElements.define('my-element', MyElement);
Attribute Change Handling
This feature allows you to handle changes to the element's attributes using the attributeChangedCallback method. You can specify which attributes to observe using the static observedAttributes getter.
class MyElement extends HTMLElement {
static get observedAttributes() { return ['data-value']; }
attributeChangedCallback(name, oldValue, newValue) {
if (name === 'data-value') {
this.shadowRoot.innerHTML = `<p>Value: ${newValue}</p>`;
}
}
}
customElements.define('my-element', MyElement);
LitElement is a simple base class for creating fast, lightweight web components. It provides a more opinionated and higher-level API compared to @webcomponents/custom-elements, including reactive properties and templating.
SkateJS is a library for writing web components with a focus on being framework-agnostic and lightweight. It offers a similar feature set to @webcomponents/custom-elements but includes additional utilities for state management and rendering.
Stencil is a compiler that generates Web Components and builds high-performance web apps. It provides a more comprehensive solution compared to @webcomponents/custom-elements, including TypeScript support, JSX templating, and a build pipeline.
A polyfill for the custom elements v1 spec.
Include a script tag at the beginning of your page, before any code that manipulates the DOM. The custom-elements.min.js can be loaded from a CDN:
<script src="https://unpkg.com/@webcomponents/custom-elements"></script>
Or you can build it yourself and then load it:
<script src="custom-elements.min.js"></script>
If you're using npm, webpack, or similar tools, this package can be installed and imported:
npm install @webcomponents/custom-elements
// Do this import before any code that manipulates the DOM.
import '@webcomponents/custom-elements';
Alternatively, you can use this polyfill via the webcomponentjs polyfills.
Install and build
npm install
npm run build
(Or, npm i && gulp
, if gulp is installed globally.)
Test
npm run test
(Or, wct
, if installed
globally.)
API which might trigger custom element reactions in the DOM
and HTML specifications are marked with the
CEReactions
extended attribute.
adoptedCallback
is not supported.CEReactions
extended attribute.
Element
for id
, className
, and slot
.DOMTokenList
(element.classList
)NamedNodeMap
(element.attributes
)Attr
(element.attributes.getNamedItem('attr-name')
)new
operator on a custom element constructor. This
means there is no way to know when a call to a constructor has begun or
finished.ParentNode
and ChildNode
interfaces do not support
DocumentFragment
s as arguments.constructor
which is that constructor.
F
, F.prototype.constructor === F
.
If you replace the prototype of your constructor F
, you must make sure
that F.prototype.constructor === F
remains true. Otherwise, the polyfill
will not be able to create or upgrade your custom elements.:defined
CSS pseudo-class
is not supported.The custom elements v1 spec is not compatible with ES5 style classes. This means
ES2015 code compiled to ES5 will not work with a native implementation of Custom
Elements.[0] While it's possible to force the custom elements polyfill to be
used to workaround this issue (by setting (customElements.forcePolyfill = true;
before loading the polyfill), you will not be using the UA's native
implementation in that case.
Since this is not ideal, we've provided an alternative: native-shim.js. Loading this shim minimally augments the native implementation to be compatible with ES5 code. We are also working on some future refinements to this approach that will improve the implementation and automatically detect if it's needed.
[0] The spec requires that an element call the HTMLElement
constructor.
Typically an ES5 style class would do something like HTMLElement.call(this)
to
emulate super()
. However, HTMLElement
must be called as a constructor and
not as a plain function, i.e. with Reflect.construct(HTMLElement, [], MyCEConstructor)
,
or it will throw.
By default, the polyfill uses a MutationObserver
to learn about and upgrade
elements in the main document as they are parsed. This MutationObserver
is
attached to document
synchronously when the script is run.
MutationObserver
earlier before loading the polyfill, that
mutation observer will not see upgraded custom elements.Note: Using polyfillWrapFlushCallback
disconnects this MutationObserver
.
customElements.polyfillWrapFlushCallback
tl;dr: The polyfill gets slower as the size of your page and number of custom
element definitions increases. You can use polyfillWrapFlushCallback
to prevent
redundant work.
To avoid a potential memory leak, the polyfill does not maintain a list of upgrade
candidates. This means that calling customElements.define
causes a synchronous,
full-document walk to search for elements with localName
s matching the new
definition. Given that this operation is potentially expensive and, if your page
loads many custom element definitions before using any of them, highly redundant,
an extra method is added to the CustomElementRegistry
prototype -
polyfillWrapFlushCallback
.
polyfillWrapFlushCallback
allows you to block the synchronous, full-document
upgrade attempts made when calling define
and perform them later. Call
polyfillWrapFlushCallback
with a function; the next time customElements.define
is called and a full-document upgrade would happen, your function will be called
instead. The only argument to your function is another function which, when
called, will run the full-document upgrade attempt.
For example, if you wanted to delay upgrades until the document's ready state
was 'complete'
, you could use the following:
customElements.polyfillWrapFlushCallback(function (flush) {
if (document.readyState === 'complete') {
// If the document is already complete, flush synchronously.
flush();
} else {
// Otherwise, wait until it is complete.
document.addEventListener('readystatechange', function () {
if (document.readyState === 'complete') {
flush();
}
});
}
});
Once your wrapper function is called (because the polyfill wants to upgrade the document), it will not be called again until you have triggered the full-document upgrade attempt. If multiple definitions are registered before you trigger upgrades, all of those definitions will apply when you trigger upgrades - don't call the provided function multiple times.
Promises returned by customElements.whenDefined
will not resolve until a
full-document upgrade attempt has been performed after the given local name
has been defined.
let flush;
customElements.polyfillWrapFlushCallback((f) => (flush = f));
const p = customElements.whenDefined('c-e');
p.then(() => console.log('c-e defined'));
customElements.define('c-e', class extends HTMLElement {});
// `p` is not yet resolved; `flush` is now a function.
flush(); // Resolves `p`; 'c-e defined' is logged.
You can't remove a callback given to polyfillWrapFlushCallback
. If the
condition your callback was intended to wait on is no longer important, your
callback should call the given function synchronously. (See the
document.readyState
example above.)
Calling polyfillWrapFlushCallback
disconnects the MutationObserver
watching
the main document. This means that you must delay until at least
document.readyState !== 'loading'
to be sure that all elements in the main
document are found (subject to exceptions mentioned in the section above).
You can call polyfillWrapFlushCallback
multiple times, each function given
will automatically wrap and delay any previous wrappers:
customElements.polyfillWrapFlushCallback(function (flush) {
console.log('added first');
flush();
});
customElements.polyfillWrapFlushCallback(function (flush) {
console.log('added second');
setTimeout(() => flush(), 1000);
});
customElements.define('c-e', class extends HTMLElement {});
// 'added second'
// ~1s delay
// 'added first'
// The document is walked to attempt upgrades.
customElements.polyfillDefineLazy
Allows defining an element with a constructor getter function that returns an
element constructor. It provides a small optimization over using
customElements.define
when producing the element class is expensive. The
constructor getter function is called only the first time the polyfill tries
to upgrade the given element.
customElements.polyfillDefineLazy('c-e', () => {
// do some expensive work then...
return class extends HTMLElement {};
});
Note, this API is not included in the custom elements spec and therefore requires use of the polyfill to function correctly.
The polyfill provides a few settings to improve performance by tweaking behavior. These settings typically have correctness trade-offs (noted below) and should be used with caution.
customElements.noDocumentConstructionObserver
: Set this flag to true to
prevent the polyfill from mutation observing and upgrading DOM as it is added
to the main document. This provides a small performance improvement during
document parsing. With this setting on, the polyfill will not upgrade elements
created when parsing the main document's HTML. This setting should be
used in conjunction with a polyfillWrapFlushCallback
that defers element
upgrades until the parser is complete.
customElements.shadyDomFastWalk
: Set this flag to true when using the
ShadyDOM polyfill to optimize how elements are found in the DOM. There are a
couple of limitations: (1) Elements that are children of Shadow DOM hosts and
are not distributed to slots may not upgrade; (2) This setting is not compatible
with using native HTML Imports.
FAQs
HTML Custom Elements Polyfill
We found that @webcomponents/custom-elements demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 4 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
MITRE's 2024 CWE Top 25 highlights critical software vulnerabilities like XSS, SQL Injection, and CSRF, reflecting shifts due to a refined ranking methodology.
Security News
In this segment of the Risky Business podcast, Feross Aboukhadijeh and Patrick Gray discuss the challenges of tracking malware discovered in open source softare.
Research
Security News
A threat actor's playbook for exploiting the npm ecosystem was exposed on the dark web, detailing how to build a blockchain-powered botnet.